package cn.tedu.csmall.passportbackup.servic.impl;

import cn.tedu.csmall.passportbackup.ex.ServiceException;
import cn.tedu.csmall.passportbackup.mapper.AdminMapper;
import cn.tedu.csmall.passportbackup.mapper.AdminRoleMapper;
import cn.tedu.csmall.passportbackup.pojo.dto.AdminAddNewDTO;
import cn.tedu.csmall.passportbackup.pojo.dto.AdminLoginDTO;
import cn.tedu.csmall.passportbackup.pojo.entity.Admin;
import cn.tedu.csmall.passportbackup.pojo.entity.AdminRole;
import cn.tedu.csmall.passportbackup.security.AdminDetails;
import cn.tedu.csmall.passportbackup.servic.IAdminService;
import cn.tedu.csmall.passportbackup.web.ServiceCode;
import com.alibaba.fastjson.JSON;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Service
public class AdminServiceImpl implements IAdminService {

    public AdminServiceImpl() {
        log.debug("");
    }

    @Value("${csmall.jwt.secrte-key}")
    private String secrteKey;
    @Value("${csmall.jwt.duration-in-minute}")
    private Long durationInMinute;
    @Autowired
    private AdminMapper adminMapper;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private AdminRoleMapper adminRoleMapper;

    /**
     * 添加管理员的业务
     * @param adminAddNewDTO 管理员数据参数
     */
    @Override
    public void addNew(AdminAddNewDTO adminAddNewDTO) {
        log.debug("开始处理【添加管理员】的业务，参数：{}",adminAddNewDTO);
        int countByUsername = adminMapper.countByUsername(adminAddNewDTO.getUsername());
        if(countByUsername > 0){
            String message = "添加失败，用户【{}】已存在";
            log.warn(message,adminAddNewDTO.getUsername());
            throw new ServiceException(ServiceCode.ERR_CONFLICT,message);
        }
        //创建admin对象
        Admin admin = new Admin();
        //调用BeanUtils.copyProperties()方法将adminAddNewDTO对象中的参数值赋予admin对象的参数
        BeanUtils.copyProperties(adminAddNewDTO,admin);
        //设置登录次数的初始值
        admin.setLoginCount(0);
        //对密码进行加密
        String password = admin.getPassword();
        String encodePassword = passwordEncoder.encode(password);
        admin.setPassword(encodePassword);
        //调用adminMapper插入数据
        log.debug("即将数据库插入数据,插入参数值：{}",admin);
        int rows = adminMapper.insert(admin);
        //判断受影响的行数
        if(rows != 1){
            String message = "服务器繁忙，请晚点再次尝试！[错误代码-1]";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERR_INTERNAL_SERVER_ERROR,message);
        }

        //向管理员数角色的关联数据表中插入数据
        log.debug("准备向管理员与角色的关联数据表中插入数据......");
        //获得本次的管理员id
        Long adminId = admin.getId();
        //获得本次的角色代号(角色id)
        Long[] roleIds = adminAddNewDTO.getRoleIds();
        AdminRole[] adminRoles = new AdminRole[roleIds.length];
        LocalDateTime now = LocalDateTime.now();
        for (int i = 0; i < roleIds.length; i++) {
            AdminRole adminRole = new AdminRole();
            adminRole.setAdminId(adminId);
            adminRole.setRoleId(roleIds[i]);
            adminRole.setGmtCreate(now);
            adminRole.setGmtModified(now);
            adminRoles[i] = adminRole;
        }
        //开始向管理员与角色的关联数据表插入数据
        log.debug("开始插入管理员与角色的关联数据......");
        rows = adminRoleMapper.insertBatch(adminRoles);
        //检查受影响的行数(受影响的行数只要大于等于1，即视为正确)
        if(rows < 1){
            String message = "服务器繁忙，请稍后重试！[错误代码-2]";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERR_INTERNAL_SERVER_ERROR,message);
        }
    }


    /**
     * 处理管理员登录业务的实现类
     * @param adminLoginDTO 封装管理员登录时的数据信息
     */
    @Override
    public String login(AdminLoginDTO adminLoginDTO) {
        log.debug("开始处理【管理员登录】的业务，传入的参数：{}",adminLoginDTO);

        //调用authenticationManager对象处理管理员认证
        Authentication authentication = new UsernamePasswordAuthenticationToken(
                adminLoginDTO.getUsername(),adminLoginDTO.getPassword());
        Authentication authenticateResult = authenticationManager.authenticate(authentication);
        log.debug("处理认证之后返回的结果：{}",authenticateResult);
        log.debug("认证结果中的Principal的类型：{}",
                authenticateResult.getPrincipal().getClass().getName());

        //处理认证结果,获取用户名
        AdminDetails loginUser = (AdminDetails)authenticateResult.getPrincipal();
        log.debug("认证结果中的管理员id：{}",loginUser.getId());
        log.debug("认证结果中的管理员用户名：{}",loginUser.getUsername());
        Collection<GrantedAuthority> authorities = loginUser.getAuthorities();
        log.debug("认证结果中的管理员权限列表：{}",authorities);
        //【重要】将权限列表转换为JSON格式，用于存储到JWT中
        String authorityListString = JSON.toJSONString(authorities);

        //jwt的盐值
        log.debug("准备生成JWT，secretKey={}",secrteKey);
        //准备生成claims
        Map<String,Object> claims = new HashMap<>();
        claims.put("id",loginUser.getId());
        claims.put("username",loginUser.getUsername());
        claims.put("authorities",authorityListString);
        log.debug("准备生成JWT，向JWT当中存入id={}",loginUser.getId());
        log.debug("准备生成JWT，向JWT当中存入useranme={}",loginUser.getUsername());
        log.debug("准备生成JWT，向JWT当中存入authorities={}",authorityListString);
        String jwt = Jwts.builder()
                //Header:用于声明算法与此数据的类型，以下配置的属性名是固定
                .setHeaderParam("alg", "HS256") //定义算法
                .setHeaderParam("typ", "jwt")  //数据的类型
                //Payload:用于添加自定义数据，并声明有效期
                .setClaims(claims)  //此处为我们向jwt保存的数据
                .setExpiration(new Date(System.currentTimeMillis() + durationInMinute * 60 * 1000)) //设置过期时间,单位是毫秒
                //Signature：用于指定算法与密钥（盐）
                .signWith(SignatureAlgorithm.HS256,secrteKey)
                .compact();
        log.debug("返回的JWT = {}",jwt);
        return jwt;
    }
}
